<script setup lang="ts">
import { Calendar, CircleClose, Delete, InfoFilled, Select, SuccessFilled, WarningFilled } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { storeToRefs } from 'pinia'
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import AppointmentSelectDialog from '../components/AppointmentSelectDialog.vue'
import { useBookingStore } from '../stores/booking'

const bookingStore = useBookingStore()
const config = storeToRefs(bookingStore).config
const loading = ref(false)
const stopping = ref(false)
const logs = ref<string[]>([])
const logMaxLines = 1000 // 最大保留日志行数
// 以上偏好与允许范围来自 store 并持久化
// 选择弹窗与选中数据
const showSelectDialog = ref(false)
const selectedAppointments = ref<any[]>([])
// 以上核心配置来自 store 并持久化
let unattendedTimer: any = null
const nextRunAt = ref<number | null>(null)
const nowTs = ref<number>(Date.now())
let nowTimer: any = null
let removeAppointmentUpdated: (() => void) | null = null

onMounted(() => {
  nowTimer = setInterval(() => { nowTs.value = Date.now() }, 1000)
  // 开始轮询日志
  startLogPolling()
  // 初次进入页面时，拉取一次已选择记录的最新状态
  refreshSelectedStatuses().catch(() => {})

  // 订阅主进程的预约更新事件，实时刷新选中项的状态
  try {
    if (window.api?.appointment?.onUpdated) {
      removeAppointmentUpdated = window.api.appointment.onUpdated((ap: any) => {
        try { applyAppointmentUpdate(ap) }
        catch {}
      })
    }
  }
  catch {}
})
onUnmounted(() => {
  clearInterval(nowTimer)
  clearTimeout(unattendedTimer)
  stopLogPolling()
  try { removeAppointmentUpdated?.() }
  catch {}
})
const unattendedLeft = computed(() => {
  if (!nextRunAt.value)
    return 0
  return Math.max(0, Math.floor((nextRunAt.value - nowTs.value) / 1000))
})

function clampConcurrency(mode: 'playwright' | 'request', desired: number): number {
  const n = Math.max(1, Number.isFinite(desired) ? desired : 1)
  const max = mode === 'playwright' ? 50 : 120
  return Math.min(n, max)
}

// 统一的开始/停止按钮状态
const isScheduled = computed(() => Boolean(config.value.unattended && nextRunAt.value))
const isActiveRun = computed(() => Boolean(loading.value || isScheduled.value))
const canStartNow = computed(() => config.value.dataSource === 'selected' ? selectedAppointments.value.length > 0 : true)
const startStopLabel = computed(() => {
  if (stopping.value)
    return '停止中...'
  if (isActiveRun.value)
    return '停止预约'
  return '开始预约'
})
const startStopType = computed(() => isActiveRun.value ? 'danger' : 'primary')
const startStopDisabled = computed(() => Boolean(stopping.value || (!isActiveRun.value && !canStartNow.value)))

interface LogEntry {
  timestamp: number
  message: string
  type: 'info' | 'success' | 'warning' | 'error'
}

const structuredLogs = ref<LogEntry[]>([])
const currentJobIds = ref<string[]>([]) // 当前活动的Job ID列表
let logPollTimer: any = null // 日志轮询定时器

// 预约状态跟踪
interface AppointmentStatus {
  id: number
  sequence_number?: string
  full_name?: string
  status: 'pending' | 'running' | 'success' | 'failed' | 'cancelled'
  progress?: number // 0-100
  message?: string
  referenceNumber?: string
  selectedDate?: string
  selectedTimeslot?: string
  startTime?: number
  endTime?: number
  nextRetryAt?: number | null
  retryCount?: number
  rawStatus?: string
  errorMessage?: string
}

const appointmentStatuses = ref<Map<number, AppointmentStatus>>(new Map())
const showAppointmentList = ref(true)
const showConfigDetails = ref(true) // 执行配置折叠状态
const totalBookingStartTime = ref<number | null>(null) // 总预约开始时间
const totalBookingEndTime = ref<number | null>(null) // 总预约结束时间

// 计算总耗时
const totalBookingDuration = computed(() => {
  if (!totalBookingStartTime.value)
    return 0
  const endTime = totalBookingEndTime.value || nowTs.value
  return Math.round((endTime - totalBookingStartTime.value) / 1000)
})

// 计算统计信息
const appointmentStats = computed(() => {
  const stats = {
    total: appointmentStatuses.value.size,
    pending: 0,
    running: 0,
    success: 0,
    failed: 0,
    cancelled: 0,
  }
  appointmentStatuses.value.forEach((status) => {
    if (status.status === 'pending')
      stats.pending++
    else if (status.status === 'running')
      stats.running++
    else if (status.status === 'success')
      stats.success++
    else if (status.status === 'failed')
      stats.failed++
    else if (status.status === 'cancelled')
      stats.cancelled++
  })
  return stats
})

const appointmentList = computed(() => {
  return Array.from(appointmentStatuses.value.values()).sort((a, b) => {
    // 按状态优先级排序：running > pending > failed > success > cancelled
    const order = { running: 0, pending: 1, failed: 2, success: 3, cancelled: 4 }
    return (order[a.status] ?? 5) - (order[b.status] ?? 5)
  })
})

// 更新预约状态
function updateAppointmentStatus(id: number, status: Partial<AppointmentStatus>) {
  const existing = appointmentStatuses.value.get(id)
  if (existing) {
    appointmentStatuses.value.set(id, { ...existing, ...status })
  }
  else {
    appointmentStatuses.value.set(id, {
      id,
      status: status.status || 'pending',
      ...status,
    } as AppointmentStatus)
  }
}

// 将主进程广播的预约对象应用到前端状态模型
function applyAppointmentUpdate(ap: any): void {
  const id = Number(ap?.id)
  if (!Number.isFinite(id))
    return
  // 仅在当前选中集合内的记录才更新“预约状态跟踪”
  const isSelected = (selectedAppointments.value || []).some(a => Number(a?.id) === id)
  if (!isSelected)
    return

  const st = String(ap?.appointment_status || '').toLowerCase()
  const mapped: Partial<AppointmentStatus> = {
    rawStatus: st,
    referenceNumber: ap?.reference_number,
    selectedDate: ap?.appointment_date,
    selectedTimeslot: ap?.appointment_time,
    retryCount: Number(ap?.retry_count || 0),
    errorMessage: ap?.error_message,
    nextRetryAt: ap?.next_retry_at ? new Date(ap.next_retry_at as any).getTime() : undefined,
  }
  if (st === 'booked') {
    mapped.status = 'success'
    mapped.progress = 100
    mapped.message = '预约成功'
    mapped.endTime = ap?.booked_at ? new Date(ap.booked_at).getTime() : Date.now()
  }
  else if (st === 'failed') {
    mapped.status = 'failed'
    mapped.progress = 100
    mapped.message = String(ap?.error_message || '预约失败')
    mapped.endTime = Date.now()
  }
  else {
    // 其它状态视为 pending（例如时间段已满/等待重试）
    mapped.status = 'pending'
    mapped.progress = 0
    mapped.message = String(ap?.error_message || '等待重试')
    mapped.endTime = undefined
  }
  updateAppointmentStatus(id, mapped)
}

// 计算每条记录距离下次重试剩余秒数
function retryLeftSeconds(item: AppointmentStatus): number | null {
  if (!item?.nextRetryAt)
    return null
  return Math.max(0, Math.floor((item.nextRetryAt - nowTs.value) / 1000))
}

// 获取状态标签颜色
function getStatusTagType(status: string): any {
  switch (status) {
    case 'success': return 'success'
    case 'failed': return 'danger'
    case 'running': return 'warning'
    case 'cancelled': return 'info'
    default: return undefined
  }
}

// 获取状态文本
function getStatusText(status: string): string {
  switch (status) {
    case 'pending': return '待处理'
    case 'running': return '预约中'
    case 'success': return '成功'
    case 'failed': return '失败'
    case 'cancelled': return '已取消'
    default: return status
  }
}

function log(msg: string, type: 'info' | 'success' | 'warning' | 'error' = 'info') {
  const entry: LogEntry = {
    timestamp: Date.now(),
    message: msg,
    type,
  }
  structuredLogs.value.push(entry)

  // 保持向后兼容：同时写入旧格式日志
  logs.value.push(`[${new Date().toLocaleTimeString()}] ${msg}`)

  // 限制日志数量，保留最新的
  if (structuredLogs.value.length > logMaxLines) {
    structuredLogs.value = structuredLogs.value.slice(-logMaxLines)
  }
  if (logs.value.length > logMaxLines) {
    logs.value = logs.value.slice(-logMaxLines)
  }

  // 自动滚动到底部
  nextTick(() => {
    const container = document.querySelector('.logs-container')
    if (container) {
      container.scrollTop = container.scrollHeight
    }
  })
}

function clearLogs() {
  structuredLogs.value = []
  logs.value = []
  ElMessage.success('日志已清空')
}

function getLogIcon(type: string) {
  switch (type) {
    case 'success':
      return SuccessFilled
    case 'warning':
      return WarningFilled
    case 'error':
      return CircleClose
    default:
      return InfoFilled
  }
}

function getLogTypeClass(type: string): string {
  switch (type) {
    case 'success':
      return 'log-success'
    case 'warning':
      return 'log-warning'
    case 'error':
      return 'log-error'
    default:
      return 'log-info'
  }
}

function formatLogTime(timestamp: number): string {
  const date = new Date(timestamp)
  const h = String(date.getHours()).padStart(2, '0')
  const m = String(date.getMinutes()).padStart(2, '0')
  const s = String(date.getSeconds()).padStart(2, '0')
  return `${h}:${m}:${s}`
}

// 恢复默认配置
function restoreDefaults() {
  if (stopping.value || isActiveRun.value)
    return
  config.value.mode = 'playwright'
  config.value.dataSource = 'selected'
  config.value.concurrency = 8
  config.value.headless = false
  config.value.monitorFirst = false
  config.value.monitorInterval = 30
  config.value.monitorOnly = false
  config.value.unattended = false
  config.value.unattendedInterval = 60
  config.value.preferredDates = ''
  config.value.preferredTimes = ''
  config.value.allowedStart = ''
  config.value.allowedEnd = ''
  ElMessage.success('已恢复默认配置')
}

// 状态提示文案
const statusText = computed(() => {
  if (stopping.value)
    return '停止中：正在取消任务...'
  if (loading.value) {
    const total = selectedAppointments.value.length || 0
    const ds = config.value.dataSource === 'auto' ? '自动抓取(全部)' : `已选择 ${total} 条`
    const ua = (config.value.unattended)
      ? `；无人值守，每${config.value.unattendedInterval}s${nextRunAt.value ? `，下次${unattendedLeft.value}s` : ''}`
      : ''
    return `执行中：${ds}，模式 ${config.value.mode} 并发 ${clampConcurrency(config.value.mode, Number(config.value.concurrency))}${ua}`
  }
  const uaIdle = (config.value.unattended)
    ? `（无人值守，每${config.value.unattendedInterval}s；下次${unattendedLeft.value || config.value.unattendedInterval}s）`
    : ''
  return `就绪：可开始预约${uaIdle}`
})

// 徽章轻微放大反馈
const badgePulse = ref(false)
watch(() => selectedAppointments.value.length, () => {
  badgePulse.value = true
  setTimeout(() => { badgePulse.value = false }, 220)
  // 选中集合变化时刷新一次状态（例如用户重新打开页面或切换筛选）
  refreshSelectedStatuses().catch(() => {})
})

// 注意：getLogClass 已废弃，使用新的结构化日志系统

// 日期快捷选择与格式化
function formatDate(d: Date): string {
  const y = d.getFullYear()
  const m = String(d.getMonth() + 1).padStart(2, '0')
  const day = String(d.getDate()).padStart(2, '0')
  return `${y}-${m}-${day}`
}

// 辅助：刷新“已选择”记录在数据库中的最新状态，更新到“预约状态跟踪”
async function refreshSelectedStatuses(): Promise<void> {
  try {
    const ids = (selectedAppointments.value || []).map(a => a?.id).filter((n: any) => Number.isFinite(n))
    if (ids.length === 0)
      return
    const rows = await Promise.all(ids.map(async (id) => {
      try {
        const res = await (window as any).api.appointment.get(id)
        return res?.success ? res.data : null
      }
      catch { return null }
    }))
    for (const a of rows) {
      if (!a || !Number.isFinite(a.id))
        continue
      try { applyAppointmentUpdate(a) }
      catch {}
    }
  }
  catch {}
}
function addDays(d: Date, days: number): Date {
  const nd = new Date(d)
  nd.setDate(nd.getDate() + days)
  return nd
}
function endOfMonth(d: Date): Date {
  return new Date(d.getFullYear(), d.getMonth() + 1, 0)
}
function onQuickDate(action: string, field: 'preferredDates' | 'allowedStart' | 'allowedEnd') {
  const today = new Date()
  if (field === 'preferredDates') {
    if (action === 'next7')
      config.value.preferredDates = formatDate(addDays(today, 7))
    else if (action === 'next14')
      config.value.preferredDates = formatDate(addDays(today, 14))
    else if (action === 'eom')
      config.value.preferredDates = formatDate(endOfMonth(today))
    else if (action === 'clear')
      config.value.preferredDates = ''
  }
  if (field === 'allowedStart') {
    if (action === 'today')
      config.value.allowedStart = formatDate(today)
    else if (action === 'next7')
      config.value.allowedStart = formatDate(addDays(today, 7))
    else if (action === 'next70')
      config.value.allowedStart = formatDate(addDays(today, 70))
    else if (action === 'clear')
      config.value.allowedStart = ''
  }
  if (field === 'allowedEnd') {
    if (action === 'eom')
      config.value.allowedEnd = formatDate(endOfMonth(today))
    else if (action === 'next30')
      config.value.allowedEnd = formatDate(addDays(today, 30))
    else if (action === 'next70')
      config.value.allowedEnd = formatDate(addDays(today, 70))
    else if (action === 'clear')
      config.value.allowedEnd = ''
  }
}

// 轻量校验（实时提示）
function isValidDateString(s: string): boolean {
  if (!s)
    return true
  const iso = /^\d{4}-\d{2}-\d{2}$/
  const eu = /^\d{2}-\d{2}-\d{4}$/
  return iso.test(s) || eu.test(s)
}
const allowedStartError = computed(() => !config.value.allowedStart || isValidDateString(config.value.allowedStart) ? '' : '日期格式不正确')
const allowedEndError = computed(() => !config.value.allowedEnd || isValidDateString(config.value.allowedEnd) ? '' : '日期格式不正确')

async function fetchAutoCandidates(): Promise<any[]> {
  try {
    const pageSize = 500
    let page = 1
    const all: any[] = []
    while (true) {
      const res = await window.api.appointment.list(
        { appointment_status: ['pending', 'failed'], is_monitor: false },
        { page, page_size: pageSize },
      )
      if (!res?.success || !Array.isArray(res.data?.appointments))
        break
      const arr = res.data.appointments as any[]
      all.push(...arr)
      if (arr.length < pageSize)
        break
      page++
      if (page > 200)
        break // safety upper bound
    }
    return all
  }
  catch {}
  return []
}

// 重新获取“已选择”的最新状态，返回仍需处理的记录（pending/failed）
async function fetchRemainingSelected(): Promise<any[]> {
  const ids = (selectedAppointments.value || []).map(a => a?.id).filter((n: any) => Number.isFinite(n))
  if (ids.length === 0)
    return []
  try {
    const results = await Promise.all(ids.map(async (id) => {
      try {
        const res = await (window as any).api.appointment.get(id)
        return res?.success ? res.data : null
      }
      catch {
        return null
      }
    }))
    return results.filter((r: any) => r && (r.appointment_status === 'pending' || r.appointment_status === 'failed'))
  }
  catch {
    return []
  }
}

async function runBooking() {
  if (config.value.dataSource === 'selected' && !selectedAppointments.value.length) {
    ElMessage.warning('请先选择未预约的记录')
    return
  }
  try {
    loading.value = true
    logs.value = []
    structuredLogs.value = [] // 清空结构化日志
    currentJobIds.value = [] // 重置Job ID列表
    // 如果之前已完成预约，开始新的预约时重置时间记录
    if (totalBookingEndTime.value) {
      totalBookingStartTime.value = Date.now() // 记录新的总开始时间
      totalBookingEndTime.value = null // 清除结束时间
    }
    // 如果之前没有开始时间，记录开始时间
    else if (!totalBookingStartTime.value) {
      totalBookingStartTime.value = Date.now() // 记录总开始时间
    }
    let list = selectedAppointments.value.map(a => ({ ...a }))

    // 初始化预约状态（如果使用已选择数据源）
    if (config.value.dataSource === 'selected' && list.length > 0) {
      // 首先检查所有预约的最新状态（包括重试成功的预约）
      const allAppointmentIds = new Set(list.map(a => a?.id).filter((id): id is number => Number.isFinite(id)))
      const updatedSuccessEndTimes: number[] = []

      for (const appointmentId of allAppointmentIds) {
        try {
          const freshStatus = await window.api.appointment.get(appointmentId)
          if (freshStatus?.success && freshStatus.data) {
            const fresh = freshStatus.data
            // 如果预约状态是 booked，需要更新状态
            if (fresh.appointment_status === 'booked') {
              const dbEndTime = fresh.booked_at ? new Date(fresh.booked_at).getTime() : null
              const existingStatus = appointmentStatuses.value.get(appointmentId)
              const currentStatus = existingStatus?.status

              // 如果之前是 pending 或 running，现在变成 booked，更新状态
              if (currentStatus === 'pending' || currentStatus === 'running' || !currentStatus) {
                const startTime = existingStatus?.startTime || totalBookingStartTime.value || Date.now()
                const endTime = dbEndTime || Date.now()

                if (dbEndTime) {
                  updatedSuccessEndTimes.push(dbEndTime)
                }

                updateAppointmentStatus(appointmentId, {
                  status: 'success',
                  progress: 100,
                  startTime,
                  endTime,
                  message: '预约成功',
                  referenceNumber: fresh.reference_number,
                  selectedDate: fresh.appointment_date,
                  selectedTimeslot: fresh.appointment_time,
                })
              }
              else if (currentStatus === 'success' && dbEndTime) {
                // 如果已经是 success，但需要更新 endTime（确保使用最新的 booked_at）
                updatedSuccessEndTimes.push(dbEndTime)
              }
            }
          }
        }
        catch {
          // 忽略错误，继续处理下一个
        }
      }

      // 如果所有预约都成功完成，更新总耗时
      if (updatedSuccessEndTimes.length > 0) {
        const latestSuccessTime = Math.max(...updatedSuccessEndTimes)
        // 检查是否所有预约都已完成（没有 pending 状态）
        const allFinished = Array.from(appointmentStatuses.value.values()).every(
          status => status.status === 'success' || status.status === 'failed',
        )
        if (allFinished && !totalBookingEndTime.value) {
          totalBookingEndTime.value = latestSuccessTime
        }
      }

      // 然后初始化状态
      list.forEach((appointment) => {
        if (appointment?.id) {
          const existingStatus = appointmentStatuses.value.get(appointment.id)
          // 如果状态已经是 success，不需要重置为 pending
          if (existingStatus?.status !== 'success') {
            updateAppointmentStatus(appointment.id, {
              id: appointment.id,
              sequence_number: appointment.sequence_number,
              full_name: appointment.full_name,
              status: 'pending',
              progress: 0,
            })
          }
        }
      })
    }
    // 无人值守/批量：若数据源为"已选择"，在发起前按最新状态过滤，仅保留 pending/failed
    if (config.value.dataSource === 'selected') {
      try {
        const remaining = await fetchRemainingSelected()
        if (Array.isArray(remaining))
          list = remaining
        if (list.length === 0) {
          log('无人值守：本轮无剩余待处理记录（已选择的数据均已成功或不存在）')
          return
        }
      }
      catch {}
    }
    if (config.value.dataSource === 'auto') {
      list = await fetchAutoCandidates()
      if (!list.length) {
        log('自动抓取：未命中候选（待处理/失败 & 非监控）。')
        if (config.value.unattended) {
          // 进入下一轮
          clearTimeout(unattendedTimer)
          const ms = Math.max(5, Number(config.value.unattendedInterval) || 60) * 1000
          nextRunAt.value = Date.now() + ms
          unattendedTimer = setTimeout(() => {
            if (!loading.value)
              runBooking().catch(() => {})
          }, ms)
          log(`无人值守：将在 ${Math.floor(ms / 1000)}s 后再次抓取执行`)
          return
        }
        else {
          ElMessage.info('未抓取到可处理的记录（待处理/失败 & 非监控）')
          return
        }
      }
      const sample = list.slice(0, 3).map(a => a.sequence_number || a.id).join(', ')
      log(`自动抓取：候选 ${list.length} 条，样例 ${sample}`)
    }
    const options: any = {
      preferredDates: config.value.preferredDates ? config.value.preferredDates.split(',').map(s => s.trim()).filter(Boolean) : undefined,
      preferredTimes: config.value.preferredTimes ? config.value.preferredTimes.split(',').map(s => s.trim()).filter(Boolean) : undefined,
      headless: config.value.mode === 'playwright' ? !!config.value.headless : undefined,
      allowedDateRange: (config.value.allowedStart || config.value.allowedEnd) ? { start: config.value.allowedStart || undefined, end: config.value.allowedEnd || undefined } : undefined,
      monitorOnly: !!config.value.monitorOnly,
    }
    const c = clampConcurrency(config.value.mode, Number(config.value.concurrency))
    const isBatch = c > 1 || list.length > 1
    if (config.value.monitorFirst) {
      // 允许用户只选择候选；监控号将自动从“监控类型”随机挑选
      const fetchRandomMonitor = async (): Promise<any | null> => {
        try {
          const res = await window.api.appointment.list(
            { is_monitor: true, appointment_status: ['pending', 'failed'] },
            { page: 1, page_size: 1000 },
          )
          const arr = res?.success ? (res.data?.appointments || []) : []
          if (Array.isArray(arr) && arr.length > 0)
            return arr[Math.floor(Math.random() * arr.length)]
        }
        catch {}
        return null
      }

      let monitor = list.find(a => a && a.is_monitor === true)
      const candidates = list.filter(a => !monitor || a.id !== monitor.id)

      if (!monitor) {
        const autoPicked = await fetchRandomMonitor()
        if (!autoPicked) {
          ElMessage.warning('未找到“监控类型”的记录，请先创建监控数据或关闭监控优先')
          loading.value = false
          return
        }
        monitor = autoPicked
      }

      if (candidates.length < 1) {
        ElMessage.warning('请至少选择1条候选记录用于触发预约')
        loading.value = false
        return
      }
      log(`启动监控优先：监控=${monitor.full_name || monitor.id} 候选=${candidates.length} 目标并发=${c}`)
      const res = await window.api.booking.monitorStart({
        monitor,
        candidates,
        concurrency: c,
        intervalMs: Math.max(10_000, config.value.monitorInterval * 1000),
        options: {
          ...options,
          mode: config.value.mode, // 传递预约模式，监控探测使用Playwright，但触发预约时使用指定模式
        },
      })
      if (res?.success) {
        ElMessage.success('监控已启动')
        log('监控已启动，检测到可约后将自动并发预约')
      }
      else {
        ElMessage.error(res?.error || '监控启动失败')
        log(`监控启动失败：${res?.error || 'unknown'}`)
      }
      return
    }
    log(`开始预约（自动判定${isBatch ? '批量' : '单条'}），数量=${list.length} 并发=${c} 模式=${config.value.mode} 无头=${config.value.mode === 'playwright' ? (config.value.headless ? '是' : '否') : '-'}`)

    if (isBatch) {
      // 更新状态为运行中
      list.forEach((appointment) => {
        if (appointment?.id) {
          updateAppointmentStatus(appointment.id, {
            status: 'running',
            progress: 10,
            startTime: Date.now(),
            message: '开始预约...',
          })
        }
      })

      const payload = { mode: config.value.mode, appointments: list, options, concurrency: c, persist: true }
      const res = await window.api.booking.bookMultiple(payload)
      if (res?.success) {
        // 尝试获取批次Job ID（如果API返回）
        if (res.data?.jobId) {
          currentJobIds.value = [res.data.jobId]
          startLogPolling()
        }
        const successCount = res.data?.success || 0
        const failedCount = res.data?.failed || 0

        // 根据API返回的详细结果更新每个预约的状态
        const results = res.data?.results || []
        const resultMap = new Map<number, any>()
        results.forEach((r: any) => {
          if (r?.id) {
            resultMap.set(r.id, r)
          }
        })

        // 预约完成后，从数据库获取最新状态来更新UI（后端已经处理好了所有状态判断）
        try {
          // 获取所有预约的最新状态（包括成功的）
          const allAppointmentPromises = list.map(async (appointment) => {
            if (!appointment?.id)
              return null
            try {
              const res = await window.api.appointment.get(appointment.id)
              return res?.success ? res.data : null
            }
            catch {
              return null
            }
          })
          const allAppointments = await Promise.all(allAppointmentPromises)
          const allAppointmentsMap = new Map<number, any>()
          allAppointments.forEach((a: any) => {
            if (a?.id) {
              allAppointmentsMap.set(a.id, a)
            }
          })

          // 获取待处理的预约（用于判断哪些还在重试）
          const remaining = await fetchRemainingSelected()
          const remainingMap = new Map<number, any>()
          remaining.forEach((r: any) => {
            if (r?.id) {
              remainingMap.set(r.id, r)
            }
          })

          // 记录第一批并发完成的时间（用于计算总耗时）
          // 找出所有成功预约的完成时间，使用最晚的那个作为第一批完成时间
          const successEndTimes: number[] = []
          const batchEndTime = Date.now()

          // 遍历所有预约，根据数据库最新状态更新UI
          list.forEach((appointment) => {
            if (!appointment?.id)
              return
            const result = resultMap.get(appointment.id)
            // 使用 allAppointmentsMap 获取完整的预约信息（包括 booked_at）
            const dbStatus = allAppointmentsMap.get(appointment.id)

            // 获取预约的实际时间（优先使用数据库中的时间）
            const dbEndTime = dbStatus?.booked_at ? new Date(dbStatus.booked_at).getTime() : null

            if (result?.success || (dbStatus && dbStatus.appointment_status === 'booked')) {
              // 预约成功（后端已更新为booked）
              // 使用数据库中的 booked_at 作为结束时间，如果没有则使用批量完成时间
              const endTime = dbEndTime || batchEndTime
              // 开始时间：使用批量开始时间（所有预约同时开始）
              const existingStatus = appointmentStatuses.value.get(appointment.id)
              const startTime = existingStatus?.startTime || totalBookingStartTime.value || Date.now()

              // 记录成功预约的完成时间
              if (dbEndTime) {
                successEndTimes.push(dbEndTime)
              }

              updateAppointmentStatus(appointment.id, {
                status: 'success',
                progress: 100,
                startTime,
                endTime,
                message: '预约成功',
                referenceNumber: result?.reference_number || dbStatus?.reference_number,
                selectedDate: result?.selected_date || dbStatus?.appointment_date,
                selectedTimeslot: result?.selected_timeslot || dbStatus?.appointment_time,
              })
            }
            else if (remainingMap.has(appointment.id)) {
              // 还在重试中的预约（pending 状态）
              const existingStatus = appointmentStatuses.value.get(appointment.id)
              const startTime = existingStatus?.startTime || totalBookingStartTime.value || Date.now()

              updateAppointmentStatus(appointment.id, {
                status: 'pending',
                progress: 0,
                startTime,
                endTime: undefined, // pending 状态不设置结束时间，因为还在重试中
                message: result?.error || '时间段已满，等待重试',
              })
            }
            else if (dbStatus) {
              // 从数据库状态判断：failed
              const existingStatus = appointmentStatuses.value.get(appointment.id)
              const startTime = existingStatus?.startTime || totalBookingStartTime.value || Date.now()

              updateAppointmentStatus(appointment.id, {
                status: 'failed',
                progress: 100,
                startTime,
                endTime: batchEndTime,
                message: result?.error || '预约失败',
              })
            }
            else {
              // 数据库中没有记录，可能已成功或不存在
              const existingStatus = appointmentStatuses.value.get(appointment.id)
              const startTime = existingStatus?.startTime || totalBookingStartTime.value || Date.now()
              updateAppointmentStatus(appointment.id, {
                status: result?.error ? 'failed' : 'success',
                progress: 100,
                startTime,
                endTime: batchEndTime,
                message: result?.error || '预约完成',
              })
            }
          })

          // 检查所有预约的最新状态（包括重试成功的预约）
          // 如果之前是 pending 的预约现在变成了 booked，需要更新状态
          const allAppointmentIds = new Set(list.map(a => a?.id).filter((id): id is number => Number.isFinite(id)))
          const finalSuccessEndTimes: number[] = []

          // 重新获取所有预约的最新状态（包括重试成功的）
          for (const appointmentId of allAppointmentIds) {
            try {
              const freshStatus = await window.api.appointment.get(appointmentId)
              if (freshStatus?.success && freshStatus.data) {
                const fresh = freshStatus.data
                // 如果预约状态是 booked，且之前是 pending，需要更新状态
                if (fresh.appointment_status === 'booked') {
                  const dbEndTime = fresh.booked_at ? new Date(fresh.booked_at).getTime() : null
                  const existingStatus = appointmentStatuses.value.get(appointmentId)
                  const currentStatus = existingStatus?.status

                  // 如果之前是 pending 或 running，现在变成 booked，更新状态
                  if (currentStatus === 'pending' || currentStatus === 'running' || !currentStatus) {
                    const startTime = existingStatus?.startTime || totalBookingStartTime.value || Date.now()
                    const endTime = dbEndTime || Date.now()

                    if (dbEndTime) {
                      finalSuccessEndTimes.push(dbEndTime)
                    }

                    updateAppointmentStatus(appointmentId, {
                      status: 'success',
                      progress: 100,
                      startTime,
                      endTime,
                      message: '预约成功',
                      referenceNumber: fresh.reference_number,
                      selectedDate: fresh.appointment_date,
                      selectedTimeslot: fresh.appointment_time,
                    })
                  }
                  else if (currentStatus === 'success' && dbEndTime) {
                    // 如果已经是 success，但需要更新 endTime（确保使用最新的 booked_at）
                    finalSuccessEndTimes.push(dbEndTime)
                  }
                }
              }
            }
            catch {
              // 忽略错误，继续处理下一个
            }
          }

          // 合并所有成功预约的完成时间
          const allSuccessEndTimes = [...successEndTimes, ...finalSuccessEndTimes]

          // 如果所有预约都成功完成，总耗时就是最晚的成功时间
          if (allSuccessEndTimes.length > 0) {
            const latestSuccessTime = Math.max(...allSuccessEndTimes)
            // 检查是否所有预约都已完成（没有 pending 状态）
            const allFinished = Array.from(appointmentStatuses.value.values()).every(
              status => status.status === 'success' || status.status === 'failed',
            )
            if (allFinished) {
              totalBookingEndTime.value = latestSuccessTime
            }
          }
        }
        catch {
          // 如果获取数据库状态失败，则使用API返回的结果
          const batchEndTime = Date.now()
          list.forEach((appointment) => {
            if (!appointment?.id)
              return
            const result = resultMap.get(appointment.id)
            const existingStatus = appointmentStatuses.value.get(appointment.id)
            const startTime = existingStatus?.startTime || totalBookingStartTime.value || Date.now()

            if (result?.success) {
              updateAppointmentStatus(appointment.id, {
                status: 'success',
                progress: 100,
                startTime,
                endTime: batchEndTime,
                message: '预约成功',
                referenceNumber: result.reference_number,
                selectedDate: result.selected_date,
                selectedTimeslot: result.selected_timeslot,
              })
            }
            else if (result?.error) {
              updateAppointmentStatus(appointment.id, {
                status: 'failed',
                progress: 100,
                startTime,
                endTime: batchEndTime,
                message: result.error,
              })
            }
          })
        }

        const logType = failedCount === 0 ? 'success' : (successCount > 0 ? 'warning' : 'error')
        const totalDuration = totalBookingStartTime.value ? Math.round((Date.now() - totalBookingStartTime.value) / 1000) : 0
        log(`批量完成：成功 ${successCount} 失败 ${failedCount}，总耗时 ${totalDuration}秒`, logType)
        ElMessage.success('预约完成')

        // 更新已选择的数据：只保留仍然待处理的记录
        if (config.value.dataSource === 'selected') {
          try {
            const remaining = await fetchRemainingSelected()
            selectedAppointments.value = remaining
          }
          catch (error) {
            console.error('更新已选择数据失败:', error)
          }
        }
      }
      else {
        const totalDuration = totalBookingStartTime.value ? Math.round((Date.now() - totalBookingStartTime.value) / 1000) : 0
        log(`预约失败：${res?.error || 'unknown'}，总耗时 ${totalDuration}秒`, 'error')
        ElMessage.error(res?.error || '预约失败')

        // 更新已选择的数据：只保留仍然待处理的记录
        if (config.value.dataSource === 'selected') {
          try {
            const remaining = await fetchRemainingSelected()
            selectedAppointments.value = remaining
          }
          catch (error) {
            console.error('更新已选择数据失败:', error)
          }
        }
      }
    }
    else {
      const appointment = list[0]
      if (appointment?.id) {
        updateAppointmentStatus(appointment.id, {
          status: 'running',
          progress: 50,
          startTime: Date.now(),
          message: '正在预约...',
        })
      }

      const payload = { mode: config.value.mode, appointment, options, persist: true }
      const res = await window.api.booking.book(payload)
      // 尝试获取单次Job ID（如果API返回）
      if (res.data?.jobId) {
        currentJobIds.value = [res.data.jobId]
        startLogPolling()
        // 立即获取一次日志
        await fetchJobLogs()
      }
      if (res.success && res.data?.success) {
        if (appointment?.id) {
          // 从数据库获取最新状态（包括 booked_at）
          try {
            const fresh = await window.api.appointment.get(appointment.id)
            if (fresh?.success && fresh.data) {
              const dbEndTime = fresh.data.booked_at ? new Date(fresh.data.booked_at).getTime() : null
              const existingStatus = appointmentStatuses.value.get(appointment.id)
              const startTime = existingStatus?.startTime || totalBookingStartTime.value || Date.now()
              const endTime = dbEndTime || Date.now()

              updateAppointmentStatus(appointment.id, {
                status: 'success',
                progress: 100,
                startTime,
                endTime,
                message: '预约成功',
                referenceNumber: res.data.referenceNumber || fresh.data.reference_number,
                selectedDate: res.data.selectedDate || fresh.data.appointment_date,
                selectedTimeslot: res.data.selectedTimeslot || fresh.data.appointment_time,
              })

              // 如果这是单条预约，更新总耗时
              if (!config.value.unattended) {
                totalBookingEndTime.value = endTime
              }
            }
            else {
              // 如果获取数据库状态失败，使用API返回的结果
              const existingStatus = appointmentStatuses.value.get(appointment.id)
              const startTime = existingStatus?.startTime || totalBookingStartTime.value || Date.now()
              updateAppointmentStatus(appointment.id, {
                status: 'success',
                progress: 100,
                startTime,
                endTime: Date.now(),
                message: '预约成功',
                referenceNumber: res.data.referenceNumber,
                selectedDate: res.data.selectedDate,
                selectedTimeslot: res.data.selectedTimeslot,
              })
            }
          }
          catch {
            // 如果获取数据库状态失败，使用API返回的结果
            const existingStatus = appointmentStatuses.value.get(appointment.id)
            const startTime = existingStatus?.startTime || totalBookingStartTime.value || Date.now()
            updateAppointmentStatus(appointment.id, {
              status: 'success',
              progress: 100,
              startTime,
              endTime: Date.now(),
              message: '预约成功',
              referenceNumber: res.data.referenceNumber,
              selectedDate: res.data.selectedDate,
              selectedTimeslot: res.data.selectedTimeslot,
            })
          }
        }
        const totalDuration = totalBookingStartTime.value ? Math.round((Date.now() - totalBookingStartTime.value) / 1000) : 0
        log(`预约成功：参考号 ${res.data.referenceNumber || '-'} 日期 ${res.data.selectedDate || '-'} 时段 ${res.data.selectedTimeslot || '-'}，总耗时 ${totalDuration}秒`, 'success')
        ElMessage.success('预约成功')

        // 更新已选择的数据：从列表中移除已成功的记录
        if (config.value.dataSource === 'selected' && appointment?.id) {
          selectedAppointments.value = selectedAppointments.value.filter(a => a?.id !== appointment.id)
        }
      }
      else {
        const errorMsg = res.error || res.data?.error || '预约失败'

        // 预约完成后，从数据库获取最新状态（后端已经处理好了所有状态判断）
        if (appointment?.id) {
          try {
            const fresh = await (window as any).api.appointment.get(appointment.id)
            if (fresh?.success && fresh.data) {
              const dbStatus = fresh.data.appointment_status
              if (dbStatus === 'booked') {
                // 虽然返回失败，但数据库显示已成功（可能是已存在预约的情况）
                updateAppointmentStatus(appointment.id, {
                  status: 'success',
                  progress: 100,
                  endTime: Date.now(),
                  message: '预约成功',
                  referenceNumber: fresh.data.reference_number,
                  selectedDate: fresh.data.appointment_date,
                  selectedTimeslot: fresh.data.appointment_time,
                })
                log(`预约成功（已存在）：参考号 ${fresh.data.reference_number || '-'}`, 'success')
                ElMessage.success('预约成功')
                // 更新已选择的数据：从列表中移除已成功的记录
                if (config.value.dataSource === 'selected') {
                  selectedAppointments.value = selectedAppointments.value.filter(a => a?.id !== appointment.id)
                }
              }
              else if (dbStatus === 'pending') {
                // 后端已判断为pending（可能是时间段已满），等待重试
                updateAppointmentStatus(appointment.id, {
                  status: 'pending',
                  progress: 0,
                  endTime: Date.now(),
                  message: `等待重试：${errorMsg}`,
                })
                const totalDuration = totalBookingStartTime.value ? Math.round((Date.now() - totalBookingStartTime.value) / 1000) : 0
                log(`等待重试：${errorMsg}，总耗时 ${totalDuration}秒`, 'warning')
                ElMessage.warning('等待重试')
              }
              else {
                // failed状态
                updateAppointmentStatus(appointment.id, {
                  status: 'failed',
                  progress: 100,
                  endTime: Date.now(),
                  message: errorMsg,
                })
                const totalDuration = totalBookingStartTime.value ? Math.round((Date.now() - totalBookingStartTime.value) / 1000) : 0
                log(`预约失败：${errorMsg}，总耗时 ${totalDuration}秒`, 'error')
                ElMessage.error(errorMsg)
              }
            }
            else {
              // 无法获取数据库状态，使用API返回的错误
              updateAppointmentStatus(appointment.id, {
                status: 'failed',
                progress: 100,
                endTime: Date.now(),
                message: errorMsg,
              })
              log(`预约失败：${errorMsg}`, 'error')
              ElMessage.error(errorMsg)
            }
          }
          catch {
            // 获取数据库状态失败，使用API返回的错误
            updateAppointmentStatus(appointment.id, {
              status: 'failed',
              progress: 100,
              endTime: Date.now(),
              message: errorMsg,
            })
            log(`预约失败：${errorMsg}`, 'error')
            ElMessage.error(errorMsg)
          }
        }
        else {
          log(`预约失败：${errorMsg}`, 'error')
          ElMessage.error(errorMsg)
        }
      }
    }
  }
  catch (e: any) {
    log(`异常：${e?.message || String(e)}`, 'error')
    ElMessage.error(e?.message || String(e))
  }
  finally {
    loading.value = false
    // 如果没有正在运行的任务，记录结束时间（但不清除开始时间，以便显示总耗时）
    if (!config.value.unattended && !loading.value) {
      if (totalBookingStartTime.value && !totalBookingEndTime.value) {
        totalBookingEndTime.value = Date.now()
      }
    }
    // 如果预约结束，继续轮询一段时间以便获取最终日志
    if (currentJobIds.value.length > 0) {
      setTimeout(() => {
        fetchJobLogs()
        // 5秒后停止轮询
        setTimeout(() => {
          stopLogPolling()
          currentJobIds.value = []
        }, 5000)
      }, 500)
    }
    // 无人值守：
    if (config.value.unattended) {
      if (config.value.dataSource === 'auto') {
        // 自动抓取：固定周期轮询
        clearTimeout(unattendedTimer)
        const ms = Math.max(5, Number(config.value.unattendedInterval) || 60) * 1000
        nextRunAt.value = Date.now() + ms
        unattendedTimer = setTimeout(() => {
          if (!loading.value)
            runBooking().catch(() => {})
        }, ms)
        log(`无人值守：将在 ${Math.floor(ms / 1000)}s 后再次抓取执行`)
      }
      else {
        // 已选择：仅当所选仍有待处理/失败时继续
        try {
          const remaining = await fetchRemainingSelected()
          if (remaining.length > 0) {
            clearTimeout(unattendedTimer)
            // 有失败记录时立即重试（只等待1秒避免CPU占用过高），无需等待轮询间隔
            const ms = 1000
            nextRunAt.value = Date.now() + ms
            unattendedTimer = setTimeout(() => {
              if (!loading.value)
                runBooking().catch(() => {})
            }, ms)
            const sample = remaining.slice(0, 3).map(a => a.sequence_number || a.id).join(', ')
            log(`无人值守：所选剩余 ${remaining.length} 条待处理/失败，样例 ${sample}；1秒后立即重试（已做反爬取处理，无需等待间隔）`)
          }
          else {
            nextRunAt.value = null
            log('无人值守：所选数据均已处理完毕，停止循环')
          }
        }
        catch {
          nextRunAt.value = null
        }
      }
    }
  }
}
async function stopBooking() {
  try {
    stopping.value = true
    // 更新所有运行中的状态为已取消
    appointmentStatuses.value.forEach((status, id) => {
      if (status.status === 'running' || status.status === 'pending') {
        updateAppointmentStatus(id, {
          status: 'cancelled',
          message: '已取消',
        })
      }
    })

    // 停止监控优先策略（如在运行）
    try { await window.api.booking.monitorStop() }
    catch {}
    const res = await window.api.booking.stop()
    if (res?.success) {
      log(`已发送停止指令，取消中的任务数=${res.data?.cancelled ?? 0}`)
      ElMessage.success('已停止预约')
      // 给主进程释放/关闭浏览器一点缓冲时间，避免按钮立即可点造成"仍可点击"的错觉
      try { await new Promise(r => setTimeout(r, 800)) }
      catch {}
    }
    else {
      log(`停止失败：${res?.error || 'unknown'}`)
      ElMessage.error(res?.error || '停止失败')
    }
  }
  catch (e: any) {
    log(`停止异常：${e?.message || String(e)}`, 'error')
    ElMessage.error(e?.message || String(e))
  }
  finally {
    stopping.value = false
    clearTimeout(unattendedTimer)
    nextRunAt.value = null
    // 记录结束时间（但不清除开始时间，以便显示总耗时）
    if (totalBookingStartTime.value && !totalBookingEndTime.value) {
      totalBookingEndTime.value = Date.now()
    }
    // 关键：用户主动停止时关闭无人值守，避免到点再次自动启动
    config.value.unattended = false
  }
}

function openSelectDialog() {
  showSelectDialog.value = true
}
watch(() => config.value.unattended, (val) => {
  if (!val) {
    clearTimeout(unattendedTimer)
    nextRunAt.value = null
  }
})

function handleSelectSuccess(list?: any[]) {
  selectedAppointments.value = Array.isArray(list) ? list : []
  // 初始化预约状态
  appointmentStatuses.value.clear()
  selectedAppointments.value.forEach((appointment) => {
    if (appointment?.id) {
      updateAppointmentStatus(appointment.id, {
        id: appointment.id,
        sequence_number: appointment.sequence_number,
        full_name: appointment.full_name,
        status: 'pending',
        progress: 0,
      })
    }
  })
  ElMessage.success(`已选择 ${selectedAppointments.value.length} 条记录`)
}

// 从Job表获取日志并合并到本地日志
async function fetchJobLogs() {
  if (currentJobIds.value.length === 0)
    return

  try {
    // 获取所有当前Job的日志
    const jobLogs: LogEntry[] = []
    for (const jobId of currentJobIds.value) {
      try {
        const res = await (window as any).api?.booking?.getJobLogs?.(jobId)
        if (res?.success && Array.isArray(res.data?.logs)) {
          // 转换Job日志格式到LogEntry格式
          for (const log of res.data.logs) {
            // 检查是否已存在（避免重复）
            const existing = structuredLogs.value.find(
              e => e.timestamp === log.timestamp && e.message === log.message,
            )
            if (!existing) {
              // 处理时间戳：可能是秒或毫秒
              let ts = log.timestamp
              if (ts && ts < 10000000000) {
                // 如果小于这个值，说明是秒，转换为毫秒（但我们的LogEntry使用毫秒）
                ts = ts * 1000
              }
              else if (!ts) {
                ts = Date.now()
              }

              jobLogs.push({
                timestamp: ts,
                message: log.message || log.step || '',
                type: mapLogLevel(log.level || log.phase || 'info'),
              })
            }
          }
        }
      }
      catch {}
    }

    // 合并新日志
    if (jobLogs.length > 0) {
      structuredLogs.value.push(...jobLogs)
      // 排序（按时间戳）
      structuredLogs.value.sort((a, b) => a.timestamp - b.timestamp)
      // 限制数量
      if (structuredLogs.value.length > logMaxLines) {
        structuredLogs.value = structuredLogs.value.slice(-logMaxLines)
      }

      // 滚动到底部
      nextTick(() => {
        const container = document.querySelector('.logs-container')
        if (container) {
          container.scrollTop = container.scrollHeight
        }
      })
    }
  }
  catch {}
}

// 映射Job日志级别到LogEntry类型
function mapLogLevel(level: string): 'info' | 'success' | 'warning' | 'error' {
  if (level.includes('error') || level.includes('fail') || level === 'danger')
    return 'error'
  if (level.includes('warn'))
    return 'warning'
  if (level.includes('success') || level.includes('complete') || level === 'success')
    return 'success'
  return 'info'
}

// 开始日志轮询
function startLogPolling() {
  stopLogPolling()
  logPollTimer = setInterval(() => {
    if (currentJobIds.value.length > 0) {
      fetchJobLogs()
    }
  }, 500) // 每500ms轮询一次
}

// 停止日志轮询
function stopLogPolling() {
  if (logPollTimer) {
    clearInterval(logPollTimer)
    logPollTimer = null
  }
}

async function toggleBooking() {
  if (isActiveRun.value) {
    await stopBooking()
  }
  else {
    await runBooking()
  }
}
</script>

<template>
  <div class="booking-modes-page">
    <!-- 页面头部 -->
    <div class="page-header">
      <div>
        <h2 class="page-title">
          预约管理
        </h2>
        <p class="page-subtitle">
          配置预约参数并执行预约任务
        </p>
      </div>
      <!-- 状态卡片 -->
      <div class="status-badge" :class="{ 'status-badge-active': isActiveRun }">
        <div class="status-indicator" :class="{ 'status-indicator-pulse': loading }" />
        <span class="status-text">{{ statusText }}</span>
      </div>
    </div>

    <!-- 配置区域 -->
    <div class="config-section">
      <!-- 数据选择与操作 -->
      <div class="config-card">
        <div class="config-card-header">
          <el-icon class="config-icon">
            <Select />
          </el-icon>
          <h3 class="config-title">
            数据选择与操作
          </h3>
        </div>
        <div class="config-card-body">
          <!-- 数据来源选择 -->
          <div class="data-source-selector">
            <label class="source-label">数据来源</label>
            <el-radio-group v-model="config.dataSource" size="large">
              <el-radio-button label="selected">
                手动选择
              </el-radio-button>
              <el-radio-button label="auto">
                自动抓取
              </el-radio-button>
            </el-radio-group>
          </div>

          <!-- 手动选择模式：显示选择按钮和已选择数量 -->
          <div v-if="config.dataSource === 'selected'" class="data-select-area">
            <div class="select-button-area">
              <el-badge :value="selectedAppointments.length" :hidden="!selectedAppointments.length" type="primary" class="badge-wrapper" :class="{ 'badge-pulse': badgePulse }">
                <el-button type="primary" :icon="Select" size="large" class="select-btn" @click="openSelectDialog">
                  选择预约数据
                </el-button>
              </el-badge>
              <div v-if="selectedAppointments.length > 0" class="selected-count">
                已选择 <strong>{{ selectedAppointments.length }}</strong> 条记录
              </div>
            </div>
            <div class="data-tip">
              仅从"未预约"数据中选择（待处理 / 失败）
            </div>
          </div>

          <!-- 自动抓取模式：显示提示信息 -->
          <div v-else class="auto-select-info">
            <div class="auto-tip">
              <el-icon><InfoFilled /></el-icon>
              <span>将自动从数据库中抓取待处理和失败的记录进行预约</span>
            </div>
          </div>

          <!-- 操作按钮 -->
          <div class="data-action-bar">
            <el-button text :disabled="stopping || isActiveRun" @click="restoreDefaults">
              恢复默认配置
            </el-button>
            <el-button :type="startStopType" size="large" :loading="stopping" :disabled="startStopDisabled" class="action-btn" @click="toggleBooking">
              {{ startStopLabel }}
            </el-button>
          </div>
        </div>
      </div>

      <!-- 预约状态跟踪 -->
      <div v-if="appointmentList.length > 0 || config.dataSource === 'selected'" class="config-card">
        <div class="config-card-header">
          <el-icon class="config-icon">
            <InfoFilled />
          </el-icon>
          <h3 class="config-title">
            预约状态跟踪
          </h3>
          <div class="flex-1" />
          <el-button text size="small" @click="showAppointmentList = !showAppointmentList">
            {{ showAppointmentList ? '收起' : '展开' }}
          </el-button>
        </div>
        <div v-show="showAppointmentList" class="config-card-body">
          <!-- 总耗时显示 -->
          <div v-if="totalBookingStartTime" class="total-duration-info">
            <el-icon><Calendar /></el-icon>
            <span>本次预约总耗时：<strong>{{ totalBookingDuration }}</strong> 秒</span>
            <span v-if="loading" class="duration-running">（进行中）</span>
          </div>
          <!-- 统计信息 -->
          <div class="stats-overview">
            <div class="stat-item">
              <div class="stat-value">
                {{ appointmentStats.total }}
              </div>
              <div class="stat-label">
                总数
              </div>
            </div>
            <div class="stat-item stat-pending">
              <div class="stat-value">
                {{ appointmentStats.pending }}
              </div>
              <div class="stat-label">
                待处理
              </div>
            </div>
            <div class="stat-item stat-running">
              <div class="stat-value">
                {{ appointmentStats.running }}
              </div>
              <div class="stat-label">
                预约中
              </div>
            </div>
            <div class="stat-item stat-success">
              <div class="stat-value">
                {{ appointmentStats.success }}
              </div>
              <div class="stat-label">
                成功
              </div>
            </div>
            <div class="stat-item stat-failed">
              <div class="stat-value">
                {{ appointmentStats.failed }}
              </div>
              <div class="stat-label">
                失败
              </div>
            </div>
            <div v-if="appointmentStats.cancelled > 0" class="stat-item stat-cancelled">
              <div class="stat-value">
                {{ appointmentStats.cancelled }}
              </div>
              <div class="stat-label">
                已取消
              </div>
            </div>
          </div>

          <!-- 预约列表 -->
          <div v-if="appointmentList.length > 0" class="appointment-list">
            <div
              v-for="item in appointmentList"
              :key="item.id"
              class="appointment-item"
              :class="`appointment-item-${item.status}`"
            >
              <div class="appointment-header">
                <div class="appointment-info">
                  <div class="appointment-name">
                    {{ item.full_name || `序列号: ${item.sequence_number || item.id}` }}
                  </div>
                  <div v-if="item.sequence_number" class="appointment-id">
                    ID: {{ item.id }} | 序列号: {{ item.sequence_number }}
                  </div>
                </div>
                <el-tag :type="getStatusTagType(item.status)" size="small" effect="dark">
                  {{ getStatusText(item.status) }}
                </el-tag>
                <el-tag
                  v-if="item.status === 'pending' && retryLeftSeconds(item) !== null && retryLeftSeconds(item)! > 0"
                  type="warning"
                  size="small"
                  class="ml-8"
                >
                  等待重试：{{ retryLeftSeconds(item) }}s
                </el-tag>
              </div>
              <div v-if="item.status === 'running'" class="appointment-progress">
                <el-progress :percentage="item.progress || 0" :status="item.progress === 100 ? 'success' : undefined" />
              </div>
              <div v-if="item.message" class="appointment-message">
                {{ item.message }}
              </div>
              <div v-if="item.referenceNumber || item.selectedDate" class="appointment-result">
                <div v-if="item.referenceNumber" class="result-item">
                  <strong>参考号：</strong>{{ item.referenceNumber }}
                </div>
                <div v-if="item.selectedDate" class="result-item">
                  <strong>日期：</strong>{{ item.selectedDate }}
                </div>
                <div v-if="item.selectedTimeslot" class="result-item">
                  <strong>时段：</strong>{{ item.selectedTimeslot }}
                </div>
              </div>
              <div v-if="item.startTime" class="appointment-time">
                开始时间：{{ new Date(item.startTime).toLocaleTimeString() }}
                <span v-if="item.endTime">
                  | 结束时间：{{ new Date(item.endTime).toLocaleTimeString() }}
                  (耗时：{{ Math.round((item.endTime - item.startTime) / 1000) }}秒)
                </span>
              </div>
            </div>
          </div>
          <div v-else-if="config.dataSource === 'selected'" class="appointment-empty">
            <el-icon><InfoFilled /></el-icon>
            <p>暂无预约记录，请先选择预约数据</p>
          </div>
        </div>
      </div>

      <!-- 执行配置 -->
      <div class="config-card">
        <div class="config-card-header">
          <el-icon class="config-icon">
            <InfoFilled />
          </el-icon>
          <h3 class="config-title">
            执行配置
          </h3>
          <div class="flex-1" />
          <el-button text size="small" @click="showConfigDetails = !showConfigDetails">
            {{ showConfigDetails ? '收起' : '展开' }}
          </el-button>
        </div>
        <el-collapse-transition>
          <div v-show="showConfigDetails" class="config-card-body">
            <div class="config-grid">
              <!-- 基础设置 -->
              <div class="config-group">
                <div class="group-header">
                  基础设置
                </div>
                <div class="group-content">
                  <div class="form-item">
                    <label class="form-label">
                      执行模式
                      <el-tooltip content="选择执行方式：Playwright 或 Request" placement="top">
                        <el-icon class="label-icon"><InfoFilled /></el-icon>
                      </el-tooltip>
                    </label>
                    <el-select v-model="config.mode" style="width: 100%">
                      <el-option label="Playwright" value="playwright" />
                      <el-option label="Request" value="request" />
                    </el-select>
                  </div>
                  <template v-if="config.mode === 'playwright'">
                    <div class="form-item">
                      <label class="form-label">
                        无头模式
                        <el-tooltip content="仅 Playwright 生效" placement="top">
                          <el-icon class="label-icon"><InfoFilled /></el-icon>
                        </el-tooltip>
                      </label>
                      <el-switch v-model="config.headless" />
                    </div>
                  </template>
                  <div class="form-item">
                    <label class="form-label">并发数</label>
                    <el-input-number v-model="config.concurrency" :min="1" :max="config.mode === 'playwright' ? 50 : 120" style="width: 100%" />
                    <div class="form-hint">
                      限制：Playwright 最大 50，Request 最大 120
                    </div>
                  </div>
                  <div class="form-item">
                    <label class="form-label">无人值守</label>
                    <el-switch v-model="config.unattended" />
                  </div>
                  <template v-if="config.unattended">
                    <div class="form-item">
                      <label class="form-label">轮询间隔（秒）</label>
                      <el-input-number v-model="config.unattendedInterval" :min="5" :max="600" style="width: 100%" />
                      <div v-if="nextRunAt" class="next-run-info">
                        <el-icon><Calendar /></el-icon>
                        <span>下次执行：{{ unattendedLeft }} 秒后</span>
                      </div>
                    </div>
                  </template>
                </div>
              </div>

              <!-- 时间偏好 -->
              <div class="config-group">
                <div class="group-header">
                  时间偏好
                </div>
                <div class="group-content">
                  <div class="form-item">
                    <label class="form-label">
                      首选日期
                      <el-tooltip content="示例：2025-12-25, 2025-12-26" placement="top">
                        <el-icon class="label-icon"><InfoFilled /></el-icon>
                      </el-tooltip>
                    </label>
                    <div class="input-with-action">
                      <el-input v-model="config.preferredDates" placeholder="示例：2025-12-25, 2025-12-26" />
                      <el-dropdown trigger="click" @command="(cmd: string) => onQuickDate(cmd, 'preferredDates')">
                        <el-button text :icon="Calendar" />
                        <template #dropdown>
                          <el-dropdown-menu>
                            <el-dropdown-item command="next7">
                              今天+7天
                            </el-dropdown-item>
                            <el-dropdown-item command="next14">
                              今天+14天
                            </el-dropdown-item>
                            <el-dropdown-item command="eom">
                              本月最后一天
                            </el-dropdown-item>
                            <el-dropdown-item divided command="clear">
                              清空
                            </el-dropdown-item>
                          </el-dropdown-menu>
                        </template>
                      </el-dropdown>
                    </div>
                  </div>
                  <div class="form-item">
                    <label class="form-label">
                      首选时段
                      <el-tooltip content="示例：09:00, 10:00" placement="top">
                        <el-icon class="label-icon"><InfoFilled /></el-icon>
                      </el-tooltip>
                    </label>
                    <el-input v-model="config.preferredTimes" placeholder="示例：09:00, 10:00" />
                  </div>
                  <div class="form-item">
                    <label class="form-label">
                      允许日期范围
                      <el-tooltip content="YYYY-MM-DD 或 DD-MM-YYYY" placement="top">
                        <el-icon class="label-icon"><InfoFilled /></el-icon>
                      </el-tooltip>
                    </label>
                    <div class="date-range-inputs">
                      <div class="input-with-action">
                        <el-input v-model="config.allowedStart" placeholder="开始日期" />
                        <el-dropdown trigger="click" @command="(cmd: string) => onQuickDate(cmd, 'allowedStart')">
                          <el-button text :icon="Calendar" />
                          <template #dropdown>
                            <el-dropdown-menu>
                              <el-dropdown-item command="today">
                                今天
                              </el-dropdown-item>
                              <el-dropdown-item command="next7">
                                今天+7天
                              </el-dropdown-item>
                              <el-dropdown-item command="next70">
                                今天+70天
                              </el-dropdown-item>
                              <el-dropdown-item divided command="clear">
                                清空
                              </el-dropdown-item>
                            </el-dropdown-menu>
                          </template>
                        </el-dropdown>
                      </div>
                      <span class="date-separator">至</span>
                      <div class="input-with-action">
                        <el-input v-model="config.allowedEnd" placeholder="结束日期" />
                        <el-dropdown trigger="click" @command="(cmd: string) => onQuickDate(cmd, 'allowedEnd')">
                          <el-button text :icon="Calendar" />
                          <template #dropdown>
                            <el-dropdown-menu>
                              <el-dropdown-item command="eom">
                                本月最后一天
                              </el-dropdown-item>
                              <el-dropdown-item command="next30">
                                今天+30天
                              </el-dropdown-item>
                              <el-dropdown-item command="next70">
                                今天+70天
                              </el-dropdown-item>
                              <el-dropdown-item divided command="clear">
                                清空
                              </el-dropdown-item>
                            </el-dropdown-menu>
                          </template>
                        </el-dropdown>
                      </div>
                    </div>
                    <div v-if="allowedStartError || allowedEndError" class="form-error">
                      {{ allowedStartError || allowedEndError }}
                    </div>
                  </div>
                </div>
              </div>

              <!-- 监控设置 -->
              <div class="config-group">
                <div class="group-header">
                  监控设置
                </div>
                <div class="group-content">
                  <div class="form-item">
                    <label class="form-label">监控优先</label>
                    <el-switch v-model="config.monitorFirst" />
                  </div>
                  <div v-if="config.monitorFirst" class="monitor-settings">
                    <div class="form-item">
                      <label class="form-label">监控轮询间隔（秒）</label>
                      <el-input-number v-model="config.monitorInterval" :min="0" :max="300" style="width: 100%" />
                    </div>
                    <div class="form-item">
                      <label class="form-label">仅监控不预约</label>
                      <el-switch v-model="config.monitorOnly" />
                    </div>
                    <div class="monitor-tip">
                      <el-icon><InfoFilled /></el-icon>
                      <span>启用后将自动使用"监控类型"的专属记录作为监控号，其余记录作为候选；发现可约后按并发执行。开启"仅监控不预约"时，只记录放号日期，不触发预约。</span>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </el-collapse-transition>
      </div>
    </div>

    <!-- 日志区域 -->
    <div class="logs-section">
      <div class="logs-header">
        <div class="logs-header-left">
          <h3 class="logs-title">
            执行日志
          </h3>
          <el-badge :value="structuredLogs.length" :hidden="!structuredLogs.length" type="info" />
        </div>
        <el-button text :icon="Delete" @click="clearLogs">
          清空日志
        </el-button>
      </div>
      <div v-if="structuredLogs.length === 0" class="logs-container logs-empty">
        <div class="empty-placeholder">
          <el-icon class="empty-icon">
            <InfoFilled />
          </el-icon>
          <p class="empty-text">
            暂无日志输出
          </p>
          <p class="empty-hint">
            开始预约后将在此显示执行日志
          </p>
        </div>
      </div>
      <div v-else class="logs-container">
        <el-timeline>
          <el-timeline-item
            v-for="(entry, index) in structuredLogs"
            :key="index"
            :icon="getLogIcon(entry.type)"
            :type="entry.type === 'error' ? 'danger' : entry.type === 'warning' ? 'warning' : entry.type === 'success' ? 'success' : 'primary'"
            :timestamp="formatLogTime(entry.timestamp)"
            placement="top"
            class="log-timeline-item"
          >
            <div class="log-message" :class="getLogTypeClass(entry.type)">
              {{ entry.message }}
            </div>
          </el-timeline-item>
        </el-timeline>
      </div>
    </div>

    <AppointmentSelectDialog
      v-model="showSelectDialog"
      batch-id=""
      @success="handleSelectSuccess"
    />
  </div>
</template>

<style scoped>
.booking-modes-page {
  max-width: 1400px;
  margin: 0 auto;
  padding: 24px;
}

/* 页面头部 */
.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 24px;
  gap: 24px;
}

.page-title {
  font-size: 28px;
  font-weight: 700;
  color: #1f2937;
  margin: 0 0 8px 0;
}

.page-subtitle {
  font-size: 14px;
  color: #6b7280;
  margin: 0;
}

.status-badge {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  background: #f9fafb;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  transition: all 0.3s;
}

.status-badge-active {
  background: #eff6ff;
  border-color: #3b82f6;
}

.status-indicator {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #9ca3af;
  flex-shrink: 0;
}

.status-badge-active .status-indicator {
  background: #3b82f6;
}

.status-indicator-pulse {
  animation: pulse 2s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
}

.status-text {
  font-size: 14px;
  color: #374151;
  font-weight: 500;
}

/* 配置区域 */
.config-section {
  margin-bottom: 24px;
}

.config-card {
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  margin-bottom: 16px;
  overflow: hidden;
  transition: box-shadow 0.3s;
}

.config-card:hover {
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}

.config-card-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 16px 20px;
  background: #f9fafb;
  border-bottom: 1px solid #e5e7eb;
}

.flex-1 {
  flex: 1;
}

.config-icon {
  font-size: 20px;
  color: #3b82f6;
}

.config-title {
  font-size: 16px;
  font-weight: 600;
  color: #1f2937;
  margin: 0;
}

.config-card-body {
  padding: 20px;
}

/* 数据选择区域 */
.data-select-area {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.badge-wrapper {
  transition: transform 0.3s;
}

.badge-pulse {
  animation: pulse-scale 0.3s ease-out;
}

@keyframes pulse-scale {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.05);
  }
  100% {
    transform: scale(1);
  }
}

.select-btn {
  font-weight: 500;
}

.selected-count {
  font-size: 14px;
  color: #6b7280;
  margin-left: 8px;
}

.selected-count strong {
  color: #3b82f6;
  font-weight: 600;
}

.data-source-selector {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 20px;
}

.select-button-area {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 16px;
}

.auto-select-info {
  margin-bottom: 20px;
}

.auto-tip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  background: #f0f9ff;
  border: 1px solid #bae6fd;
  border-radius: 8px;
  color: #0369a1;
  font-size: 14px;
}

.auto-tip .el-icon {
  font-size: 18px;
  color: #0284c7;
}

.data-action-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 20px;
  border-top: 1px solid #e5e7eb;
  margin-top: 20px;
}

.source-label {
  font-size: 14px;
  font-weight: 500;
  color: #374151;
  white-space: nowrap;
}

.data-tip {
  font-size: 12px;
  color: #9ca3af;
  line-height: 1.5;
}

/* 配置网格 */
.config-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}

@media (min-width: 1024px) {
  .config-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.config-group {
  background: #f9fafb;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
}

.group-header {
  padding: 12px 16px;
  background: #f3f4f6;
  border-bottom: 1px solid #e5e7eb;
  font-size: 14px;
  font-weight: 600;
  color: #374151;
}

.group-content {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.form-item {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.form-label {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 13px;
  font-weight: 500;
  color: #374151;
}

.label-icon {
  font-size: 14px;
  color: #9ca3af;
  cursor: help;
}

.form-hint {
  font-size: 12px;
  color: #9ca3af;
  margin-top: 4px;
}

.form-error {
  font-size: 12px;
  color: #ef4444;
  margin-top: 4px;
}

.input-with-action {
  display: flex;
  gap: 4px;
  align-items: center;
}

.date-range-inputs {
  display: flex;
  flex-direction: column;
}

.date-separator {
  color: #9ca3af;
  font-size: 14px;
  flex-shrink: 0;
}

.next-run-info {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  padding: 8px 12px;
  background: #eff6ff;
  border: 1px solid #bfdbfe;
  border-radius: 6px;
  font-size: 13px;
  color: #1e40af;
}

.next-run-info .el-icon {
  font-size: 16px;
}

.monitor-settings {
  margin-top: 8px;
  padding: 16px;
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
}

.monitor-tip {
  display: flex;
  gap: 8px;
  padding: 12px;
  background: #f0f9ff;
  border: 1px solid #bae6fd;
  border-radius: 6px;
  font-size: 12px;
  color: #0369a1;
  line-height: 1.6;
}

.monitor-tip .el-icon {
  font-size: 16px;
  color: #0284c7;
  flex-shrink: 0;
  margin-top: 2px;
}

.action-btn {
  min-width: 120px;
  font-weight: 500;
}

/* 日志区域 */
.logs-section {
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  overflow: hidden;
}

.logs-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  background: #f9fafb;
  border-bottom: 1px solid #e5e7eb;
}

.logs-header-left {
  display: flex;
  align-items: center;
  gap: 12px;
}

.logs-title {
  font-size: 16px;
  font-weight: 600;
  color: #1f2937;
  margin: 0;
}

.logs-container {
  min-height: 300px;
  max-height: 600px;
  padding: 20px;
  overflow-y: auto;
  scroll-behavior: smooth;
  background: #fafafa;
}

.logs-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 300px;
}

.empty-placeholder {
  text-align: center;
  color: #9ca3af;
}

.empty-icon {
  font-size: 48px;
  margin-bottom: 12px;
  opacity: 0.5;
}

.empty-text {
  font-size: 14px;
  font-weight: 500;
  margin-bottom: 4px;
  color: #6b7280;
}

.empty-hint {
  font-size: 12px;
  opacity: 0.7;
}

.log-timeline-item {
  padding-bottom: 12px;
}

.log-message {
  font-size: 13px;
  line-height: 1.6;
  word-break: break-word;
  padding: 4px 0;
}

.log-info {
  color: #6b7280;
}

.log-success {
  color: #10b981;
  font-weight: 500;
}

.log-warning {
  color: #f59e0b;
  font-weight: 500;
}

.log-error {
  color: #ef4444;
  font-weight: 500;
}

/* Timeline 样式优化 */
:deep(.el-timeline-item__timestamp) {
  color: #9ca3af;
  font-size: 12px;
  margin-bottom: 4px;
}

:deep(.el-timeline-item__wrapper) {
  padding-left: 20px;
}

:deep(.el-timeline-item__tail) {
  border-left-color: #e5e7eb;
}

:deep(.el-timeline-item__node) {
  width: 14px;
  height: 14px;
}

:deep(.el-timeline-item__node--primary) {
  background-color: #3b82f6;
}

:deep(.el-timeline-item__node--success) {
  background-color: #10b981;
}

:deep(.el-timeline-item__node--warning) {
  background-color: #f59e0b;
}

:deep(.el-timeline-item__node--danger) {
  background-color: #ef4444;
}

/* 输入聚焦态增强 */
:deep(.el-input__wrapper.is-focus),
:deep(.el-select .el-select__wrapper.is-focused),
:deep(.el-input-number.is-focus .el-input-number__decrease),
:deep(.el-input-number.is-focus .el-input-number__increase) {
  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);
  border-color: #3b82f6;
}

/* 预约状态跟踪样式 */
.stats-overview {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  gap: 12px;
  margin-bottom: 20px;
  padding: 16px;
  background: #f9fafb;
  border-radius: 8px;
}

.stat-item {
  text-align: center;
  padding: 12px;
  background: white;
  border-radius: 6px;
  border: 1px solid #e5e7eb;
  transition: all 0.3s;
}

.stat-item:hover {
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  transform: translateY(-2px);
}

.stat-item.stat-pending {
  border-color: #d1d5db;
}

.stat-item.stat-running {
  border-color: #fbbf24;
  background: #fffbeb;
}

.stat-item.stat-success {
  border-color: #10b981;
  background: #ecfdf5;
}

.stat-item.stat-failed {
  border-color: #ef4444;
  background: #fef2f2;
}

.stat-item.stat-cancelled {
  border-color: #9ca3af;
  background: #f9fafb;
}

.stat-value {
  font-size: 24px;
  font-weight: 700;
  color: #1f2937;
  margin-bottom: 4px;
}

.stat-item.stat-pending .stat-value {
  color: #6b7280;
}

.stat-item.stat-running .stat-value {
  color: #f59e0b;
}

.stat-item.stat-success .stat-value {
  color: #10b981;
}

.stat-item.stat-failed .stat-value {
  color: #ef4444;
}

.stat-item.stat-cancelled .stat-value {
  color: #9ca3af;
}

.stat-label {
  font-size: 12px;
  color: #6b7280;
  font-weight: 500;
}

.appointment-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-height: 600px;
  overflow-y: auto;
}

.appointment-item {
  padding: 16px;
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  transition: all 0.3s;
}

.appointment-item:hover {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.appointment-item-running {
  border-color: #fbbf24;
  background: #fffbeb;
  animation: pulse-border 2s ease-in-out infinite;
}

@keyframes pulse-border {
  0%, 100% {
    border-color: #fbbf24;
  }
  50% {
    border-color: #f59e0b;
  }
}

.appointment-item-success {
  border-color: #10b981;
  background: #ecfdf5;
}

.appointment-item-failed {
  border-color: #ef4444;
  background: #fef2f2;
}

.appointment-item-cancelled {
  border-color: #9ca3af;
  background: #f9fafb;
  opacity: 0.8;
}

.appointment-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 12px;
}

.appointment-info {
  flex: 1;
}

.appointment-name {
  font-size: 15px;
  font-weight: 600;
  color: #1f2937;
  margin-bottom: 4px;
}

.appointment-id {
  font-size: 12px;
  color: #6b7280;
}

.appointment-progress {
  margin: 12px 0;
}

.appointment-message {
  font-size: 13px;
  color: #6b7280;
  margin: 8px 0;
  padding: 8px 12px;
  background: #f9fafb;
  border-radius: 4px;
}

.appointment-item-running .appointment-message {
  background: #fef3c7;
  color: #92400e;
}

.appointment-result {
  margin: 12px 0;
  padding: 12px;
  background: #f0f9ff;
  border: 1px solid #bae6fd;
  border-radius: 6px;
}

.appointment-item-success .appointment-result {
  background: #d1fae5;
  border-color: #6ee7b7;
}

.result-item {
  font-size: 13px;
  color: #0369a1;
  margin-bottom: 4px;
  line-height: 1.6;
}

.result-item:last-child {
  margin-bottom: 0;
}

.result-item strong {
  color: #1e40af;
  font-weight: 600;
}

.appointment-item-success .result-item {
  color: #065f46;
}

.appointment-item-success .result-item strong {
  color: #047857;
}

.appointment-time {
  font-size: 12px;
  color: #9ca3af;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid #e5e7eb;
}

.appointment-empty {
  text-align: center;
  padding: 40px 20px;
  color: #9ca3af;
}

.appointment-empty .el-icon {
  font-size: 48px;
  margin-bottom: 12px;
  opacity: 0.5;
}

.appointment-empty p {
  font-size: 14px;
  margin: 0;
}

/* 总耗时信息 */
.total-duration-info {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  margin-bottom: 16px;
  background: #f0f9ff;
  border: 1px solid #bae6fd;
  border-radius: 8px;
  font-size: 14px;
  color: #0369a1;
}

.total-duration-info .el-icon {
  font-size: 18px;
  color: #0284c7;
}

.total-duration-info strong {
  font-size: 16px;
  font-weight: 600;
  color: #1e40af;
}

.total-duration-info .duration-running {
  margin-left: 8px;
  font-size: 12px;
  color: #0284c7;
  font-style: italic;
}

/* 响应式优化 */
@media (max-width: 1024px) {
  .page-header {
    flex-direction: column;
    align-items: flex-start;
  }

  .status-badge {
    width: 100%;
  }

  .config-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 768px) {
  .booking-modes-page {
    padding: 16px;
  }

  .page-title {
    font-size: 24px;
  }

  .data-source-selector {
    flex-direction: column;
    align-items: flex-start;
  }

  .data-action-bar {
    flex-direction: column-reverse;
    gap: 12px;
  }

  .action-btn {
    width: 100%;
  }

  .date-range-inputs {
    flex-direction: column;
    align-items: stretch;
  }

  .date-separator {
    display: none;
  }
}
</style>
